#pragma once

#include <list>

#include "Checkout.h"
#include "Distributor.h"
#include "Profit.h"
#include "LoadCurve.h"
#include "Log.h"
#include "Config.h"

class GasStation
{
public:

	GasStation(std::vector<int> distributorsCapacity, std::vector<int> checkoutsCapacity, std::vector<int> loadCurve)
		: time(0), carsDiscarded(0), carsTanked(0), lastCarId(0)
	{		
		distributors = new std::list<Distributor *>();

		for(int i = 0; i < distributorsCapacity.size(); i++)
		{
			distributors->push_back(new Distributor(distributorsCapacity[i]));
		}

		checkouts = new std::list<Checkout *>();

		for(int i = 0; i < checkoutsCapacity.size(); i++)
		{
			checkouts->push_back(new Checkout(checkoutsCapacity[i]));
		}

		curve = new LoadCurve(loadCurve);
	}

	virtual ~GasStation(void)
	{
	}	

	int GetDistributorsCount()
	{
		return distributors->size();
	}

	int GetCheckoutsCount()
	{
		return checkouts->size();
	}

	Machine *GetMachine(int index)
	{
		Machine *m = NULL;

		m = index < GetDistributorsCount() ? 
			(Machine *)ElementAt(distributors, index) :
			(Machine *)ElementAt(checkouts, index - GetDistributorsCount());		

		return m;
	}

	void Simulate()
	{
		time++;

		for(std::list<Checkout *>::iterator i = checkouts->begin(); i != checkouts->end(); i++)
		{
			if((*i)->HasTask())
			{
				if((*i)->WorkFinished())
				{
					profit.push_back(Profit(time, ((Car *)(*i)->GetClient()->GetCar())->GetLiters() * Config::GasPrice));
					Log::Info("%d Car: %d is leaving the station\n", GetSeconds(), ((Car *)(*i)->GetClient()->GetCar())->GetId());

					((Distributor *)((Car *)(*i)->GetClient()->GetCar())->GetDistributor())->MoveToNextTask();
					(*i)->MoveToNextTask();		

					carsTanked++;
					
					//delete (*i)->GetClient()->GetCar();
				}
				else
				{
					(*i)->DoWork();
				}
			}
		}

		for(std::list<Distributor *>::iterator i = distributors->begin(); i != distributors->end(); i++)
		{
			if((*i)->HasTask())
			{
				if((*i)->WorkFinished())
				{
					if(!(*i)->GetCar()->IsPaying())
					{
						Checkout *c = FindWithLowestLoad<Checkout>(checkouts);

						if(c != NULL)
						{
							c->EnqueueWork((*i)->GetCar()->GetClient());
							(*i)->GetCar()->SetIsPaying(true);
							(*i)->GetCar()->SetDistributor((*i));		

							Log::Info("%d Car: %d is tanked, going to pay\n", GetSeconds(), (*i)->GetCar()->GetId());
						}
					}
				}
				else
				{
					(*i)->DoWork();
				}
			}
		}

		if(ShouldSpawnCar())
		{
			Car *c = SpawnCar();
			Distributor *d = FindWithLowestLoad<Distributor>(distributors);

			if(d != NULL)
			{
				d->EnqueueWork(c);
				Log::Info("%d Car: %d is spawned\n", GetSeconds(), c->GetId());
			}
			else
			{
				DiscardCar(c);
				Log::Info("%d Car: %d is discarded\n", GetSeconds(), c->GetId());
			}
		}
	}
	
	int GetSeconds()
	{
		return time * Config::SecPerTick;
	}

	float GetIncome()
	{
		return GetProfit(INT_MAX);
	}

	float GetIncome1h()
	{
		return GetProfit(60 * 60 / Config::SecPerTick);
	}

	float GetIncome12h()
	{
		return GetProfit(12 * 60 * 60 / Config::SecPerTick);
	}

	float GetIncome24h()
	{
		return GetProfit(24 * 60 * 60 / Config::SecPerTick);
	}

	int GetHappyClients()
	{
		return carsTanked;
	}

	int GetDiscardedClients()
	{
		return carsDiscarded;
	}

	float GetProfit(int timeBack)
	{
		int p = 0;		

		for(std::list<Profit>::reverse_iterator i = profit.rbegin(); i != profit.rend() && i->GetTime() > time - timeBack; i++)
		{
			p += i->GetMoney();			
		}

		return (float)p;
	}

	float GetLastCurveValue()
	{
		return lastCurveValue;
	}

private:
	std::list<Checkout *> *checkouts;
	std::list<Distributor *> *distributors;
	std::list<Profit> profit;

	LoadCurve *curve;

	int time;

	int carsDiscarded;
	int carsTanked;
	int lastCarId;
	float lastCurveValue;

	bool ShouldSpawnCar()
	{
		lastCurveValue = curve->GetValue(time);
		return Generator::GetUniform(0, 100) < lastCurveValue;
	}

	Car* SpawnCar()
	{
		return new Car(lastCarId++);
	}

	void DiscardCar(Car *c)
	{
		delete c;
		carsDiscarded++;
	}

	template <class T>
	T *FindWithLowestLoad(std::list<T *> *items)
	{
		T* value = NULL;

		for(std::list<T *>::iterator i = items->begin(); i != items->end(); i++)
		{
			if((*i)->HasMoreSpace() && (value == NULL || value->GetCount() > (*i)->GetCount()))
			{
				value = (*i);
			}
		}

		return value;
	}

	template <class T>
	T *ElementAt(std::list<T *> *items, int index)
	{
		T *value = NULL;
		int elem =0 ;

		for(std::list<T *>::iterator i = items->begin(); i != items->end() && elem <= index; i++, elem++)
		{
			value = *i;
		}

		return value;
	}
};

